﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AcademicPlanner.Domain.Models;
using AcademicPlanner.Framework.Helpers;

namespace AcademicPlanner.Domain.Components
{
	/// <summary>
	/// AcademicPlanningMngr is the component of the AcademicPlanner application
	/// responsible for managing a user's academic plan, academic courses and
	/// programs under it.
	/// </summary>
	/// <since>1.0</since>
	public class AcademicPlanningMngr : Component
	{
		AcademicPlanningMngr() { }

		class InstanceHolder
		{
			internal static AcademicPlanningMngr INSTANCE = new AcademicPlanningMngr();
		}


		internal static AcademicPlanningMngr GetInstance()
		{
			return InstanceHolder.INSTANCE;
		}

		/// <summary>
		/// Gets the <c>Planner</c> whose <c>User</c> has the provided
		/// username.
		/// </summary>
		/// <param name="pUserName">The username.</param>
		/// <returns>The <c>Planner</c> or <c>null</c>.</returns>
		/// <seealso cref="P:DomainMaster"/>
		/// <seealso cref="P:AcademicPlanner.Domain.DomainMaster.MainModel"/>
		/// <seealso cref="P:AcademicPlanner.Domain.Models.MainModel.AllPlanners"/>
		/// <seealso cref="M:AcademicPlanner.Domain.Models.Collections.PlannerCollection.WithUserName"/>
		/// <since>1.0</since>
		public Planner GetPlanner(string pUserName)
		{
			return DomainMaster.MainModel.AllPlanners.WithUserName(pUserName);
		}

		/// <summary>
		/// Gets the <c>AcademicPlan</c> of the <c>Planner</c> whose
		/// <c>User</c> has the provided username.
		/// </summary>
		/// <param name="pPlannerUserName">The username.</param>
		/// <returns>The <c>AcademicPlan</c> or <c>null</c>.</returns>
		public AcademicPlan GetAcademicPlan(string pPlannerUserName)
		{
			var vPlanner = GetPlanner(pPlannerUserName);
			return (vPlanner != null) ? vPlanner.Plan : null;
		}

		/// <summary>
		/// Gets the <c>AcademicPlan</c> with the given identifier.
		/// </summary>
		/// <param name="pEntityId">The identifier.</param>
		/// <returns>The <c>AcademicPlan</c> or <c>null</c>.</returns>
		/// <seealso cref="P:DomainMaster"/>
		/// <seealso cref="P:AcademicPlanner.Domain.DomainMaster.MainModel"/>
		/// <seealso cref="P:AcademicPlanner.Domain.Models.MainModel.AllPlans"/>
		/// <seealso cref="M:AcademicPlanner.Domain.Models.Collections.AcademicPlanCollection.WithId"/>
		/// <since>1.0</since>
		public AcademicPlan GetAcademicPlan(int pEntityId)
		{
			return DomainMaster.MainModel.AllPlans.WithId(pEntityId);
		}

		/// <summary>
		/// Gets the <c>Program</c> with the given identifier.
		/// </summary>
		/// <param name="pEntityId">The identifier.</param>
		/// <returns>The <c>Program</c> or <c>null</c>.</returns>
		/// <seealso cref="P:DomainMaster"/>
		/// <seealso cref="P:AcademicPlanner.Domain.DomainMaster.MainModel"/>
		/// <seealso cref="P:AcademicPlanner.Domain.Models.MainModel.AllPrograms"/>
		/// <seealso cref="M:AcademicPlanner.Domain.Models.Collections.ProgramCollection.WithId"/>
		/// <since>1.0</since>
		public Program GetProgram(int pEntityId)
		{
			return DomainMaster.MainModel.AllPrograms.WithId(pEntityId);
		}

		/// <summary>
		/// Gets the <c>Course</c> with the given identifier.
		/// </summary>
		/// <param name="pEntityId">The identifier.</param>
		/// <returns>The <c>Course</c> or <c>null</c>.</returns>
		/// <seealso cref="P:DomainMaster"/>
		/// <seealso cref="P:AcademicPlanner.Domain.DomainMaster.MainModel"/>
		/// <seealso cref="P:AcademicPlanner.Domain.Models.MainModel.AllCourses"/>
		/// <seealso cref="M:AcademicPlanner.Domain.Models.Collections.CourseCollection.WithId"/>
		/// <since>1.0</since>
		public Course GetCourse(int pEntityId)
		{
			return DomainMaster.MainModel.AllCourses.WithId(pEntityId);
		}

		/// <summary>
		/// Updates and saves a <c>AcademicPlan</c> data.
		/// </summary>
		/// <param name="pPlanId">The identifier of the <c>AcademicPlan</c>.</param>
		/// <param name="pCreditUnits">The common number of credits for academic courses.</param>
		/// <param name="pTermIndices">
		/// The indices of the <c>AcademicPlan</c>'s <c>Terms</c> to act on;
		/// indices paired with <c>false</c> are removed and indices paired
		/// with <c>true</c> are updated or added if they don't exist.
		/// </param>
		/// <param name="pTermNames">
		/// The new names of the <c>AcademiPlan</c>'s <c>Terms</c> at the paired
		/// indices.
		/// </param>
		/// <param name="pTermOrderNums">
		/// The new positions in the year of the <c>AcademiPlan</c>'s <c>Terms</c>
		/// at the paired indices.
		/// </param>
		/// <param name="pDivisionIndices">
		/// The indices of the <c>AcademicPlan</c>'s <c>Divisions</c> to act on;
		/// indices paired with <c>false</c> are removed and indices paired
		/// with <c>true</c> are updated or added if they don't exist.
		/// </param>
		/// <param name="pDivisionNames">
		/// The new names of the <c>AcademiPlan</c>'s <c>Divisions</c> at the paired
		/// indices.
		/// </param>
		/// <param name="pDivisionCategoryIndices">
		/// The indices of the <c>AcademicPlan</c>'s <c>Divisions</c>' <c>Categories</c>
		/// to act on; indices paired with <c>false</c> are removed and indices paired
		/// with <c>true</c> are updated or added if they don't exist.
		/// </param>
		/// <param name="pDivisionCategoryNames">
		/// The new names of the <c>AcademiPlan</c>'s <c>Divisions</c>' <c>Categories</c>
		/// at the paired indices.
		/// </param>
		/// <param name="pDivisionCategoryCodes">
		/// The new codes of the <c>AcademiPlan</c>'s <c>Divisions</c>' <c>Categories</c>
		/// at the paired indices.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown.
		/// <c>InvalidModelPropertiesException</c>s may be thrown.
		/// <c>NotAuthorizedException</c>s may be thrown in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_ACADEMICPLAN_TO_SAVE_NOT_FOUND</c>
		/// if the <c>AcademiPlan</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetAcademicPlan(int)"/>
		/// <since>1.0</since>
		public void SaveAcademicPlan(int pPlanId, double pCreditUnits, IDictionary<int, bool> pTermIndices,
										IDictionary<int, string> pTermNames, IDictionary<int, int> pTermOrderNums,
										IDictionary<int, bool> pDivisionIndices,
										IDictionary<int, string> pDivisionNames,
										IDictionary<int, IDictionary<int, bool>> pDivisionCategoryIndices,
										IDictionary<int, IDictionary<int, string>> pDivisionCategoryNames,
										IDictionary<int, IDictionary<int, string>> pDivisionCategoryCodes)
		{
			var vPlan = GetAcademicPlan(pPlanId);
			if (vPlan == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_ACADEMICPLAN_TO_SAVE_NOT_FOUND);

			var vTask = new SaveAcademicPlanTask(vPlan, pCreditUnits, pTermIndices, pTermNames,
													pTermOrderNums, pDivisionIndices, pDivisionNames,
													pDivisionCategoryIndices, pDivisionCategoryNames,
													pDivisionCategoryCodes);
			vTask.Run();
		}

		/// <summary>
		/// Creates and saves a <c>Program</c>.
		/// </summary>
		/// <param name="pPlanId">
		/// The identifier of the <c>AcademicPlan</c> to create the <c>Program</c> under.
		/// </param>
		/// <param name="pProgramName">The name of the <c>Program</c>.</param>
		/// <param name="pProgramMinCredits">
		/// The minimum number of credits required for the <c>Program</c>.
		/// </param>
		/// <param name="pProgramRequiredCourseIds">
		/// The identifiers of the <c>Course</c>s that are required for the <c>Program</c>.</param>
		/// <param name="pProgramReommendedCourseIds">
		/// The identifiers of the <c>Course</c>s that are recommended for the <c>Program</c>.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown.
		/// <c>InvalidModelPropertiesException</c>s may be thrown.
		/// <c>NotAuthorizedException</c>s may be thrown in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_ACADEMICPLAN_TO_SAVE_NEW_PROGRAM_UNDER_NOT_FOUND</c>
		/// if the <c>AcademiPlan</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetAcademicPlan(int)"/>
		/// <since>1.0</since>
		public void SaveNewProgram(int pPlanId, string pProgramName, double pProgramMinCredits,
										IList<int> pProgramRequiredCourseIds, IList<int> pProgramReommendedCourseIds)
		{
			var vPlan = GetAcademicPlan(pPlanId);
			if (vPlan == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_ACADEMICPLAN_TO_SAVE_NEW_PROGRAM_UNDER_NOT_FOUND);

			var vTask = new SaveNewProgramTask(vPlan, pProgramName, pProgramMinCredits, pProgramRequiredCourseIds,
												pProgramReommendedCourseIds);
			vTask.Run();
		}

		/// <summary>
		/// Creates and saves a <c>Course</c>.
		/// </summary>
		/// <param name="pPlanId">
		/// The identifier of the <c>AcademicPlan</c> to create the <c>Course</c> under.</param>
		/// <param name="pCourseName">The name of the <c>Course</c>.</param>
		/// <param name="pCourseNumber">The number of the <c>Course</c>.</param>
		/// <param name="pCourseCredits">The credits the <c>Course</c> is worth.</param>
		/// <param name="pCourseCategoryId">The identifier of the <c>CourseCategory</c> of the <c>Course</c>.</param>
		/// <param name="pCoursePrerequisiteIds">
		/// The identifiers of the <c>Course</c>s that are prerequisite to the <c>Course</c>.</param>
		/// <param name="pCourseCommonlyOfferedInTermIds">
		/// The identifiers of the <c>AcademicTerm</c>s that the <c>Course</c> is commonly offered in.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown indirectly.
		/// <c>InvalidModelPropertiesException</c>s may be thrown indirectly.
		/// <c>NotAuthorizedException</c>s may be thrown indirectly in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_ACADEMICPLAN_TO_SAVE_NEW_COURSE_UNDER_NOT_FOUND</c>
		/// if the <c>AcademiPlan</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetAcademicPlan(int)"/>
		/// <since>1.0</since>
		public void SaveNewCourse(int pPlanId, string pCourseName, int pCourseNumber,
									double pCourseCredits, int pCourseCategoryId, IList<int> pCoursePrerequisiteIds, 
									IList<int> pCourseCommonlyOfferedInTermIds)
		{
			var vPlan = GetAcademicPlan(pPlanId);
			if (vPlan == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_ACADEMICPLAN_TO_SAVE_NEW_COURSE_UNDER_NOT_FOUND);

			var vTask = new SaveNewCourseTask(vPlan, pCourseName, pCourseNumber, pCourseCredits, pCourseCategoryId,
												pCoursePrerequisiteIds, pCourseCommonlyOfferedInTermIds);
			vTask.Run();
		}

		/// <summary>
		/// Updates and saves a <c>Course</c> data.
		/// </summary>
		/// <param name="pCourseId">The identifier of the <c>Course</c>.</param>
		/// <param name="pCourseName">The name of the <c>Course</c>.</param>
		/// <param name="pCourseNumber">The number of the <c>Course</c>.</param>
		/// <param name="pCourseCredits">The credits the <c>Course</c> is worth.</param>
		/// <param name="pCourseCategoryId">The identifier of the <c>CourseCategory</c> of the <c>Course</c>.</param>
		/// <param name="pCoursePrerequisiteIds">
		/// The identifiers of the <c>Course</c>s that are prerequisite to the <c>Course</c>.</param>
		/// <param name="pCourseCommonlyOfferedInTermIds">
		/// The identifiers of the <c>AcademicTerm</c>s that the <c>Course</c> is commonly offered in.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown indirectly.
		/// <c>InvalidModelPropertiesException</c>s may be thrown indirectly.
		/// <c>NotAuthorizedException</c>s may be thrown indirectly in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_COURSE_TO_SAVE_NOT_FOUND</c>
		/// if the <c>Course</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetCourse"/>
		/// <since>1.0</since>
		public void SaveCourse(int pCourseId, string pCourseName, int pCourseNumber,
									double pCourseCredits, int pCourseCategoryId, IList<int> pCoursePrerequisiteIds,
									IList<int> pCourseCommonlyOfferedInTermIds)
		{
			var vCourse = GetCourse(pCourseId);
			if (vCourse == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_COURSE_TO_SAVE_NOT_FOUND);

			var vTask = new SaveCourseTask(vCourse, pCourseName, pCourseNumber, pCourseCredits, pCourseCategoryId,
											pCoursePrerequisiteIds, pCourseCommonlyOfferedInTermIds);
			vTask.Run();
		}

		/// <summary>
		/// Updates and saves a <c>Program</c> data.
		/// </summary>
		/// <param name="pProgramId">The identifier of the <c>Program</c>.</param>
		/// <param name="pProgramName">The new name of the <c>Program</c>.</param>
		/// <param name="pProgramMinCredits">
		/// The new minimum of credits required for the <c>Program</c>.
		/// </param>
		/// <param name="pProgramRequiredCourseIds">
		/// The identifiers of the <c>Course</c>s that are required for the <c>Program</c>.</param>
		/// <param name="pProgramReommendedCourseIds">
		/// The identifiers of the <c>Course</c>s that are recommended for the <c>Program</c>.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown.
		/// <c>InvalidModelPropertiesException</c>s may be thrown.
		/// <c>NotAuthorizedException</c>s may be thrown in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_PROGRAM_TO_SAVE_NOT_FOUND</c>
		/// if the <c>Program</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetProgram"/>
		/// <since>1.0</since>
		public void SaveProgram(int pProgramId, string pProgramName, double pProgramMinCredits,
										IList<int> pProgramRequiredCourseIds, IList<int> pProgramReommendedCourseIds)
		{
			var vProg = GetProgram(pProgramId);
			if (vProg == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_PROGRAM_TO_SAVE_NOT_FOUND);

			var vTask = new SaveProgramTask(vProg, pProgramName, pProgramMinCredits, pProgramRequiredCourseIds,
												pProgramReommendedCourseIds);
			vTask.Run();
		}

		/// <summary>
		/// Deletes a <c>Program</c>.
		/// </summary>
		/// <remarks>
		/// The <c>Program</c> is essentially trashed but not
		/// completely deleted from the storage.
		/// </remarks>
		/// <param name="pProgramId">The identifier of the <c>Program</c>.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown.
		/// <c>InvalidModelPropertiesException</c>s may be thrown.
		/// <c>NotAuthorizedException</c>s may be thrown in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_PROGRAM_TO_DELETE_NOT_FOUND</c>
		/// if the <c>Program</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetProgram"/>
		/// <since>1.0</since>
		public void DeleteProgram(int pProgramId)
		{
			var vProg = GetProgram(pProgramId);
			if (vProg == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_PROGRAM_TO_DELETE_NOT_FOUND);

			var vTask = new DeleteProgramTask(vProg);
			vTask.Run();
		}

		/// <summary>
		/// Deletes a <c>Course</c>.
		/// </summary>
		/// <remarks>
		/// The <c>Course</c> is essentially trashed but not
		/// completely deleted from the storage.
		/// </remarks>
		/// <param name="pCourseId">The identifier of the <c>Course</c>.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown indirectly.
		/// <c>InvalidModelPropertiesException</c>s may be thrown indirectly.
		/// <c>NotAuthorizedException</c>s may be thrown indirectly in case of access control
		/// restrictions.
		/// </remarks>
		/// <exception cref="T:AcademicPlanner.Domain.NotCriticalDomainException">
		/// Thrown with error code <c>DomainErrorCode.C_COURSE_TO_DELETE_NOT_FOUND</c>
		/// if the <c>Course</c> with the given identifier does not exist.
		/// </exception>
		/// <seealso cref="M:GetCourse"/>
		/// <since>1.0</since>
		public void DeleteCourse(int pCourseId)
		{
			var vCourse = GetCourse(pCourseId);
			if (vCourse == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_COURSE_TO_DELETE_NOT_FOUND);

			var vTask = new DeleteCourseTask(vCourse);
			vTask.Run();
		}


		class DeleteCourseTask : Task
		{
			Course course;

			internal DeleteCourseTask(Course pCourse)
			{
				course = pCourse;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.DELETE_COURSE, course))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_DELETE_COURSE);

				base.Init();
			}

			protected override void Perform()
			{
				course.Delete();
			}
		}

		class DeleteProgramTask : Task
		{
			Program program;

			internal DeleteProgramTask(Program pProgram)
			{
				program = pProgram;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.DELETE_PROGRAM, program))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_DELETE_PROGRAM);

				base.Init();
			}

			protected override void Perform()
			{
				program.Delete();
			}
		}

		class SaveProgramTask : Task
		{
			Program program;
			string name;
			double ? minCredits;
			IList<int> requiredCourseIds;
			IList<int> reommendedCourseIds;

			internal SaveProgramTask(Program pProgram, string pName, double? pMinCredits,
										IList<int> pRequiredCourseIds, IList<int> pReommendedCourseIds)
			{
				program = pProgram;
				name = pName;
				minCredits = pMinCredits;
				requiredCourseIds = pRequiredCourseIds;
				reommendedCourseIds = pReommendedCourseIds;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.UPDATE_PROGRAM, program))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_UPDATE_PROGRAM);

				base.Init();
			}

			protected override void Perform()
			{
				var vReqCrses = program.Plan.GetCoursesWithId(requiredCourseIds);
				var vRecmdCrses = program.Plan.GetCoursesWithId(reommendedCourseIds);
				var vOp1 = new UpdateProgramOperation(program, name, minCredits, vReqCrses, vRecmdCrses);

				vOp1.Launch();
			}
		}

		class SaveCourseTask : Task
		{
			Course course;
			string name;
			int? number;
			double? credits;
			int? categoryId;
			IList<int> prerequisiteIds;
			IList<int> commonlyOfferedInTermIds;

			internal SaveCourseTask(Course pCourse, string pName, int? pNumber,
										double? pCredits, int? pCategoryId,
										IList<int> pPrerequisiteIds,
										IList<int> pCommonlyOfferedInTermIds)
			{
				course = pCourse;
				name = pName;
				number = pNumber;
				credits = pCredits;
				categoryId = pCategoryId;
				prerequisiteIds = pPrerequisiteIds;
				commonlyOfferedInTermIds = pCommonlyOfferedInTermIds;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.UPDATE_COURSE, course))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_UPDATE_COURSE);

				base.Init();
			}

			protected override void Perform()
			{
				CourseCategory vCat = null;
				if (categoryId != null)
				{
					vCat = course.Plan.GetCategoryWithId((int)categoryId);
					if (vCat == null)
						throw new InvalidModelPropertiesException("Course", "Category",
							InvalidModelPropertyErrorCode.NOT_PRESENT,
							DomainErrorCode.C_NOT_PRESENT_COURSE_CATEGORY);
				}

				var vPrereqs = course.Plan.GetCoursesWithId(prerequisiteIds);
				var vCommonTerms = course.Plan.GetTermsWithId(commonlyOfferedInTermIds);
				var vOp1 = new UpdateCourseOperation(course, name, number, credits, vCat,
														vPrereqs, vCommonTerms);

				vOp1.Launch();
			}
		}

		class SaveNewCourseTask : Task
		{
			AcademicPlan plan;
			string courseName;
			int ? courseNumber;
			double ? courseCredits;
			int? courseCategoryId;
			IList<int> coursePrerequisiteIds;
			IList<int> courseCommonlyOfferedInTermIds;

			internal SaveNewCourseTask(AcademicPlan pPlan, string pCourseName, int ? pCourseNumber,
										double? pCourseCredits, int? pCourseCategoryId,
										IList<int> pCoursePrerequisiteIds, 
										IList<int> pCourseCommonlyOfferedInTermIds)
			{
				plan = pPlan;
				courseName = pCourseName;
				courseNumber = pCourseNumber;
				courseCredits = pCourseCredits;
				courseCategoryId = pCourseCategoryId;
				coursePrerequisiteIds = pCoursePrerequisiteIds;
				courseCommonlyOfferedInTermIds = pCourseCommonlyOfferedInTermIds;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.CREATE_COURSE, plan))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_CREATE_COURSE);

				base.Init();
			}

			protected override void Perform()
			{
				var vPrereqs = plan.GetCoursesWithId(coursePrerequisiteIds);
				var vCommonTerms = plan.GetTermsWithId(courseCommonlyOfferedInTermIds);
				var vOp1 = new CreateCourseOperation(plan, courseName, courseNumber, courseCredits, courseCategoryId,
														vPrereqs, vCommonTerms);

				vOp1.Launch();
			}

			protected override void Succeeded()
			{
				// setup the new course for Access Control
				DomainMaster.Auth.RegisterIntoACSystem(plan.Courses.ToList().Last(),
							InstanceHolder.INSTANCE.GetPlanner(SessionHelper.GetUserName()).User);
			}
		}

		class SaveNewProgramTask : Task
		{
			AcademicPlan plan;
			string programName;
			double? programMinCredits;
			IList<int> programRequiredCourseIds;
			IList<int> programReommendedCourseIds;

			internal SaveNewProgramTask(AcademicPlan pPlan, string pProgramName, double? pProgramMinCredits,
										IList<int> pProgramRequiredCourseIds, IList<int> pProgramReommendedCourseIds)
			{
				plan = pPlan;
				programName = pProgramName;
				programMinCredits = pProgramMinCredits;
				programRequiredCourseIds = pProgramRequiredCourseIds;
				programReommendedCourseIds = pProgramReommendedCourseIds;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.CREATE_PROGRAM, plan))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_CREATE_PROGRAM);

				base.Init();
			}

			protected override void Perform()
			{
				var vReqCrses = plan.GetCoursesWithId(programRequiredCourseIds);
				var vRecmdCrses = plan.GetCoursesWithId(programReommendedCourseIds);
				var vOp1 = new CreateProgramOperation(plan, programName, programMinCredits, vReqCrses, vRecmdCrses);

				vOp1.Launch();
			}

			protected override void Succeeded()
			{
				// setup the new program for Access Control
				DomainMaster.Auth.RegisterIntoACSystem(plan.Programs.ToList().Last(), 
							InstanceHolder.INSTANCE.GetPlanner(SessionHelper.GetUserName()).User);
			}
		}

		class SaveAcademicPlanTask : Task
		{
			AcademicPlan plan;
			double ? creditUnits;
			IDictionary<int, bool> termIndices;
			IDictionary<int, string> termNames;
			IDictionary<int, int> termOrderNums;
			IDictionary<int, bool> divisionIndices;
			IDictionary<int, string> divisionNames;
			IDictionary<int, IDictionary<int, bool>> divisionCategoryIndices;
			IDictionary<int, IDictionary<int, string>> divisionCategoryNames;
			IDictionary<int, IDictionary<int, string>> divisionCategoryCodes;

			internal SaveAcademicPlanTask(AcademicPlan pPlan, double ? pCreditUnits,
											IDictionary<int, bool> pTermIndices,
											IDictionary<int, string> pTermNames,
											IDictionary<int, int> pTermOrderNums,
											IDictionary<int, bool> pDivisionIndices,
											IDictionary<int, string> pDivisionNames,
											IDictionary<int, IDictionary<int, bool>> pDivisionCategoryIndices,
											IDictionary<int, IDictionary<int, string>> pDivisionCategoryNames,
											IDictionary<int, IDictionary<int, string>> pDivisionCategoryCodes)
			{
				plan = pPlan;
				creditUnits = pCreditUnits;
				termIndices = pTermIndices;
				termNames = pTermNames;
				termOrderNums = pTermOrderNums;
				divisionIndices = pDivisionIndices;
				divisionNames = pDivisionNames;
				divisionCategoryIndices = pDivisionCategoryIndices;
				divisionCategoryNames = pDivisionCategoryNames;
				divisionCategoryCodes = pDivisionCategoryCodes;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.UPDATE_ACADEMICPLAN, plan))
					throw new NotAuthorizedException(DomainErrorCode.C_NOT_AUTHORIZED_UPDATE_ACADEMICPLAN);

				base.Init();
			}

			protected override void Perform()
			{
				var vOp1 = new UpdateAcademicPlanOperation(plan, creditUnits, termIndices,
															termNames, termOrderNums, divisionIndices,
															divisionNames, divisionCategoryIndices,
															divisionCategoryNames, divisionCategoryCodes);
				vOp1.Launch();
			}

			protected override void Failed()
			{
				DomainMaster.ModelsFountain.LoseModel(plan);
			}

			protected override void Succeeded()
			{
				DomainMaster.ModelsFountain.LoseModel(plan);
				return;
			}
		}

		class UpdateCourseOperation : Operation
		{
			protected Course course;
			protected string name;
			protected int ? number;
			protected double ? credits;
			protected CourseCategory category;
			protected IList<Course> prerequisites;
			protected IList<AcademicTerm> commonlyOfferedInTerms;

			internal UpdateCourseOperation(Course pCourse, string pName, int ? pNumber, double ? pCredits,
											CourseCategory pCategory, IList<Course> pRerequisites,
											IList<AcademicTerm> pCommonlyOfferedInTerms)
			{
				course = pCourse;
				name = pName;
				number = pNumber;
				credits = pCredits;
				category = pCategory;
				prerequisites = pRerequisites;
				commonlyOfferedInTerms = pCommonlyOfferedInTerms;
			}

			protected override void Execute()
			{
				DomainMaster.Auth.EnterPrivelegedAuthorizationMode();

				var vName = name ?? course.Name;
				var vNumber = number ?? course.Number;
				var vCredits = credits ?? course.Credits;
				var vCat = category ?? course.Category;

				DomainMaster.Auth.ExitPrivelegedAuthorizationMode();

				course.Set(vName, vNumber, vCredits, vCat);
				foreach (var vCourse in course.Prerequisites.ToList())
				{
					if (prerequisites.Contains(vCourse))
					{
						course.AddPrerequisite(vCourse);
						prerequisites.Remove(vCourse);
					}
					else
						course.RemovePrerequisite(vCourse);
				}
				foreach (var vCourse in prerequisites)
				{
					course.AddPrerequisite(vCourse);
				}
				foreach (var vCourse in course.CommonlyOfferedInTerms.ToList())
				{
					if (commonlyOfferedInTerms.Contains(vCourse))
					{
						course.AddCommonlyOfferedInTerm(vCourse);
						commonlyOfferedInTerms.Remove(vCourse);
					}
					else
						course.RemoveCommonlyOfferedInTerm(vCourse);
				}
				foreach (var vCourse in commonlyOfferedInTerms)
				{
					course.AddCommonlyOfferedInTerm(vCourse);
				}
			}
		}

		class CreateCourseOperation : UpdateCourseOperation
		{
			AcademicPlan plan;

			internal Course Course { get { return course; } }

			internal CreateCourseOperation(AcademicPlan pPlan, string pName, int ? pNumber,
											double? pCredits, int? pCategoryId, IList<Course> pRerequisites,
											IList<AcademicTerm> pCommonlyOfferedInTerms)
				: base(null, pName, pNumber, pCredits, null, pRerequisites, pCommonlyOfferedInTerms)
			{
				plan = pPlan;
				if (pCategoryId != null)
				{
					category = plan.GetCategoryWithId((int)pCategoryId);
					if (category == null)
						throw new InvalidModelPropertiesException("Course", "Category",
							InvalidModelPropertyErrorCode.NOT_PRESENT,
							DomainErrorCode.C_NOT_PRESENT_COURSE_CATEGORY);
				}
			}

			protected override void Execute()
			{
				course = DomainMaster.ModelsFountain.GetNewCourse();
				base.Execute();
				plan.AddCourse(course);
				course.Persit();
			}
		}

		class UpdateProgramOperation : Operation
		{
			protected Program program;
			protected string name;
			protected double ? minCredits;
			protected IList<Course> requiredCourses;
			protected IList<Course> recommendedCourses;

			internal UpdateProgramOperation(Program pProgram, string pName, double ? pMinCredits,
											IList<Course> pRequiredCourses, IList<Course> pRecommendedCourses)
			{
				program = pProgram;
				name = pName;
				minCredits = pMinCredits;
				requiredCourses = pRequiredCourses;
				recommendedCourses = pRecommendedCourses;
			}

			protected override void Execute()
			{
				program.Set(name, minCredits);
				foreach (var vCourse in program.RequiredCourses.ToList())
				{
					if (requiredCourses.Contains(vCourse))
					{
						program.AddRequiredCourse(vCourse);
						requiredCourses.Remove(vCourse);
					}
					else
						program.RemoveRequiredCourse(vCourse);
				}
				foreach (var vCourse in requiredCourses)
				{
					program.AddRequiredCourse(vCourse);
				}
				foreach (var vCourse in program.RecommendedCourses.ToList())
				{
					if (recommendedCourses.Contains(vCourse))
					{
						program.AddRecommendedCourse(vCourse);
						recommendedCourses.Remove(vCourse);
					}
					else
						program.RemoveRecommendedCourse(vCourse);
				}
				foreach (var vCourse in recommendedCourses)
				{
					program.AddRecommendedCourse(vCourse);
				}
			}
		}

		class CreateProgramOperation : UpdateProgramOperation
		{
			AcademicPlan plan;

			internal Program Program { get { return program; } }

			internal CreateProgramOperation(AcademicPlan pPlan, string pName, double? pMinCredits,
											IList<Course> pRequiredCourses, IList<Course> pRecommendedCourses)
				: base(null, pName, pMinCredits, pRequiredCourses, pRecommendedCourses)
			{
				plan = pPlan;
			}

			protected override void Execute()
			{
				program = DomainMaster.ModelsFountain.GetNewProgram();
				base.Execute();
				plan.AddProgram(program);
				program.Persit();
			}
		}

		internal class CreatePlannerOperation : Operation
		{
			Planner planner;
			User user;

			internal Planner Planner { get { return planner; } }

			internal CreatePlannerOperation(User pUser)
			{
				user = pUser;
			}

			protected override void Execute()
			{
				DomainMaster.Auth.EnterPrivelegedAuthorizationMode();
				planner = DomainMaster.MainModel.AllPlanners.WithUserName(user.Name);
				if (planner != null)
					throw new CriticalDomainException(DomainErrorCode.C_NOT_UNIQUE_PLANNER_USER_NAME);

				planner = DomainMaster.ModelsFountain.GetNewPlanner();
				planner.InitUser(user);
				planner.Persit();
				DomainMaster.Auth.ExitPrivelegedAuthorizationMode();
			}
		}

		class UpdateAcademicPlanOperation : Operation
		{
			AcademicPlan plan;
			double ? creditUnits;
			IDictionary<int, bool> termIndices;
			IDictionary<int, string> termNames;
			IDictionary<int, int> termOrderNums;
			IDictionary<int, bool> divisionIndices;
			IDictionary<int, string> divisionNames;
			IDictionary<int, IDictionary<int, bool>> divisionCategoryIndices;
			IDictionary<int, IDictionary<int, string>> divisionCategoryNames;
			IDictionary<int, IDictionary<int, string>> divisionCategoryCodes;

			internal UpdateAcademicPlanOperation(AcademicPlan pPlan, double ? pCreditUnits,
													IDictionary<int, bool> pTermIndices,
													IDictionary<int, string> pTermNames,
													IDictionary<int, int> pTermOrderNums,
													IDictionary<int, bool> pDivisionIndices,
													IDictionary<int, string> pDivisionNames,
													IDictionary<int, IDictionary<int, bool>> pDivisionCategoryIndices,
													IDictionary<int, IDictionary<int, string>> pDivisionCategoryNames,
													IDictionary<int, IDictionary<int, string>> pDivisionCategoryCodes)
			{
				plan = pPlan;
				creditUnits = pCreditUnits;
				termIndices = pTermIndices;
				termNames = pTermNames;
				termOrderNums = pTermOrderNums;
				divisionIndices = pDivisionIndices;
				divisionNames = pDivisionNames;
				divisionCategoryIndices = pDivisionCategoryIndices;
				divisionCategoryNames = pDivisionCategoryNames;
				divisionCategoryCodes = pDivisionCategoryCodes;
			}

			protected override void Execute()
			{
				var vTerms = plan.Terms.ToList();
				var vTermsRemoveQueue = new List<int>();
				var vTermsAddQueue = new List<AcademicTerm>();

				plan.Set(creditUnits);

				try
				{
					foreach (var vPair in termIndices)
					{
						if (vPair.Value)
						{
							if (vPair.Key >= 0 && vPair.Key < vTerms.Count)
								vTerms[vPair.Key].Set(termNames[vPair.Key], termOrderNums[vPair.Key]);
							else
							{
								var vTerm = DomainMaster.ModelsFountain.GetNewAcademicTerm();
								vTerm.Set(termNames[vPair.Key], termOrderNums[vPair.Key]);
								vTermsAddQueue.Add(vTerm);
							}
						}
						else if (!termIndices[vPair.Key] && vTerms[vPair.Key] != null)
							vTermsRemoveQueue.Add(vPair.Key);
					}
				}
				catch (InvalidModelPropertiesException vIx)
				{
					var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");
					vErrs.AddInner("Terms", vIx.Errors);
					throw new InvalidModelPropertiesException(DomainErrorCode.C_INVALID_ACADEMICPLAN_TERMS_PROPERTIES,
																vErrs);
				}
				foreach (var vIndex in vTermsRemoveQueue)
				{
					plan.RemoveTerm(vIndex);
				}
				foreach (var vTerm in vTermsAddQueue)
				{
					plan.AddTerm(vTerm);
					vTerm.Persit();
				}

				var vDivisions = plan.Divisions.ToList();
				var vDivsRemoveQueue = new List<int>();
				var vDivsAddQueue = new Dictionary<int, CourseDivision>();

				try
				{
					foreach (var vPair in divisionIndices)
					{
						if (vPair.Value)
						{
							if (vPair.Key >= 0 && vPair.Key < vDivisions.Count)
								UpdateDivision(vDivisions[vPair.Key], vPair.Key);
							else
							{
								var vDiv = DomainMaster.ModelsFountain.GetNewCourseDivision();
								vDivsAddQueue.Add(vPair.Key, vDiv);
							}

						}
						else if (!divisionIndices[vPair.Key] && vDivisions[vPair.Key] != null)
							vDivsRemoveQueue.Add(vPair.Key);
					}
				}
				catch (InvalidModelPropertiesException vIx)
				{
					var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");
					vErrs.AddInner("Divisions", vIx.Errors);
					throw new InvalidModelPropertiesException(DomainErrorCode.C_INVALID_ACADEMICPLAN_DIVISIONS_PROPERTIES,
																vErrs);
				}
				foreach (var vIndex in vDivsRemoveQueue)
				{
					plan.RemoveDivision(vIndex);
				}
				foreach (var vPair in vDivsAddQueue)
				{
					plan.AddDivision(vPair.Value);
					vPair.Value.Persit();
					UpdateDivision(vPair.Value, vPair.Key);
				}
			}

			protected void UpdateDivision(CourseDivision pDivision, int pIndex)
			{
				var vName = divisionNames[pIndex];
				var vCategories = pDivision.Categories.ToList();
				var vRemoveQueue = new List<int>();
				var vAddQueue = new List<CourseCategory>();

				pDivision.Set(vName);

				try
				{
					foreach (var vPair in divisionCategoryIndices[pIndex])
					{
						if (vPair.Value)
						{
							var vCatName = divisionCategoryNames[pIndex] != null
											? divisionCategoryNames[pIndex][vPair.Key]
											: null;
							var vCatCode = divisionCategoryCodes[pIndex] != null
											? divisionCategoryCodes[pIndex][vPair.Key]
											: null;

							if (vPair.Key >= 0 && vPair.Key < vCategories.Count)
								vCategories[vPair.Key].Set(vCatName, vCatCode);
							else
							{
								var vCat = DomainMaster.ModelsFountain.GetNewCourseCategory();
								vCat.Set(vCatName, vCatCode);
								vAddQueue.Add(vCat);
							}
						}
						else if (!divisionCategoryIndices[pIndex][vPair.Key])
						{
							vRemoveQueue.Add(vPair.Key);
						}
					}
				}
				catch (InvalidModelPropertiesException vIx)
				{
					var vErrs = new InvalidModelPropertiesErrorCollection("CourseDivision");
					vErrs.AddInner("Categories", vIx.Errors);
					throw new InvalidModelPropertiesException(DomainErrorCode.C_INVALID_ACADEMICPLAN_DIVISIONS_CATEGORIES_PROPERTIES,
																vErrs);
				}
				foreach (var vIndex in vRemoveQueue)
				{
					pDivision.RemoveCategory(vIndex);
				}
				foreach (var vCat in vAddQueue)
				{
					pDivision.AddCategory(vCat);
					vCat.Persit();
				}	
			}
		}
	}
}
